# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import sys

import svn.core

import gvn.cmdline
import gvn.util


helptext__gvn_diff = """\
diff (di): Display the differences for locally modified paths.
usage: diff [-c CHANGE | PATH...]

  Display the changes made to the paths changed in CHANGE or PATHS.
  Without CHANGE or PATHs, display all changes in the working copy.
"""

# TODO(epg): This is nearly identical with the Status block in opened
# (and maybe others); put this somewhere common.  Name sucks too...
def GetModified(ctx, changebranched=None, modified=None):
  """Call changebranched or modified with (target, status) of modified paths.

  Arguments:
  ctx               -- run recursive status on ctx.path
  changebranched    -- called for changebranched paths
  modified          -- called for paths modified but not changebranched

  """

  # XXX(epg): Hmm, seems to me the callback ought to be receiving a
  # pool, like most callbacks.  Fine, we'll handle it ourselves.
  pool = svn.core.Pool(ctx.pool)
  def cb(target, status):
    pool.clear()
    # We're only interested in Modified or Broken paths.
    if gvn.wc.IsModified(status) or gvn.wc.IsBroken(status):
      target = ctx.wc.RelativePath(target)
      try:
        change_name = ctx.wc.change_state[target].change_name
      except KeyError:
        if callable(modified):
          modified(target, status)
      else:
        if callable(changebranched):
          changebranched(target, status)
  ctx.wc.Status([ctx.path], recursive=True, get_all=False, no_ignore=False,
                show_children=False, callback=cb)

def Handle_GvnDiff(ctx):
  # Three different commands in one:

  ######################################################################
  # 1. diff --from-change -c foo
  if ctx.options.from_change:
    change = ctx.options.change
    if change is None:
      raise gvn.errors.BadOptions("Can't use --from-change without -c")
    if change == 'none':
      raise gvn.errors.BadOptions("Can't use --from-change with -c none")

    targets = set()
    (username, name, revision) = gvn.util.ParseChangeName(change)
    cb = gvn.changebranch.ChangeBranch(ctx.config, ctx.project,
                                       name, username, revision)
    return gvn.changebranch.DiffSnapshotToWC(cb, ctx.wc, ctx.GetDiffCallbacks(),
                                             ctx.encoding, ctx.pool)

  targets = set(ctx.operands)

  ######################################################################
  # 2. diff -c none
  if ctx.options.change == 'none':
    ctx.wc.Open([ctx.path])
    GetModified(ctx, modified=lambda t,s: targets.add(t))
    if not targets:
      # All modified paths are changebranched and no operands were
      # specified; show nothing.
      return 0
    ctx.options.gvn_options.remove('change')

  ######################################################################
  # 3. diff [-c foo] [PATHs]
  elif ctx.options.change is not None:
    for (path, state) in ctx.wc.change_state.iteritems():
      if state.change_name == ctx.options.change:
        # It seems odd to call get the DisplayPath here, but really it
        # isn't.  path is relative to the wc top, so directly passing
        # that to svn only works if current directory == wc top, which
        # is frequently not true.  DisplayPath localizes (fine, svn
        # expects that anyway) and converts to cwd-relative.
        targets.add(ctx.DisplayPath(path))
    ctx.options.gvn_options.remove('change')
  return gvn.cmdline.RunSvnCommand(ctx, 'diff', sorted(targets))

gvn.cmdline.AddCommand('diff', Handle_GvnDiff, helptext__gvn_diff,
                       ['change', 'non-recursive', 'diff-cmd', 'extensions',
                        'no-diff-deleted', 'notice-ancestry', 'from-change',
                        'force'],
                       {'change': 'changebranch ARG'},
                       aliases=['di'])
